package v1

import (
	"fmt"
	"hash/fnv"
	"io"
	"sync"
	"sync/atomic"
)

type ObjectRef struct {
	// 互斥锁效率稍高
	lk   sync.Mutex
	refN atomic.Int32

	key   string
	owner *RefObjectList
	inner interface{}
}

func (this *ObjectRef) Value() any {
	return this.inner
}

func (this *ObjectRef) RefCount() int32 {
	return this.refN.Load()
}

func (this *ObjectRef) incRef() int32 {
	return this.refN.Add(1)
}

// 需要确保自身不被释放
func (this *ObjectRef) acquire(conf string) error {
	this.lk.Lock()
	defer this.lk.Unlock()
	if this.inner == nil {
		obj, err := this.owner.newFn(conf)
		if err != nil {
			return err
		}
		this.inner = obj
	}
	return nil
}

func (this *ObjectRef) decRef() int32 {
	return this.refN.Add(-1)
}

var (
	ErrorClosed      = fmt.Errorf("The object has been closed.")
	defRefObjectList = NewRefObjectList()
)

type shard struct {
	lk  sync.RWMutex
	lst map[string]*ObjectRef
}

// 引用对象， 有做分片
type RefObjectList struct {
	shs        []shard
	lk         sync.Mutex
	shardCount int
	closeFn    func(obj any) (err error)
	newFn      func(conf string) (obj interface{}, err error)
}

func NewRefObjectList() *RefObjectList {
	shardCount := 16
	shs := make([]shard, shardCount)
	for i := range shs {
		shs[i].lst = make(map[string]*ObjectRef)
	}
	return &RefObjectList{
		shs:        shs,
		shardCount: shardCount,
		newFn:      nullCreateFunc,
	}
}
func nullCreateFunc(conf string) (obj interface{}, err error) {
	return nil, fmt.Errorf("CreateObjectFunc is null")
}

func (this *RefObjectList) SetNewObjectFunc(fn func(conf string) (any, error)) {
	this.lk.Lock()
	defer this.lk.Unlock()
	if fn == nil {
		fn = nullCreateFunc
	}
	this.newFn = fn
}

// 根据 key 计算分片索引
func (this *RefObjectList) getShard(conf string) *shard {
	hash := fnv.New32a()
	hash.Write([]byte(conf))
	return &this.shs[hash.Sum32()%uint32(this.shardCount)]
}

func (this *RefObjectList) SetCloseObjectFunc(fn func(obj any) error) {
	this.lk.Lock()
	defer this.lk.Unlock()
	this.closeFn = fn
}

func (this *RefObjectList) tryGet(sh *shard, conf string) *ObjectRef {
	sh.lk.RLock()
	defer sh.lk.RUnlock()
	itm := sh.lst[conf]
	if itm != nil {
		// 锁内加锁确保不被释放
		itm.incRef()
		return itm
	}

	return nil
}

func (this *RefObjectList) closeInner(obj any) error {
	fn := this.closeFn
	if fn != nil {
		return fn(obj)
	}
	if intf, ok := obj.(io.Closer); ok {
		return intf.Close()
	}
	return nil
}

func (this *RefObjectList) acquire(handle any) any {
	if handle == nil {
		return nil
	}

	if ref, ok := handle.(*ObjectRef); ok {
		return ref.Value()
	}

	return nil
}

func (this *RefObjectList) Acquire(conf string) (handle any, object any, err error) {
	handle, err = this.acquireHandle(conf)
	if err != nil {
		return nil, nil, err
	}
	object = this.acquire(handle)
	return
}

func (this *RefObjectList) acquireHandle(conf string) (handle any, err error) {
	sd := this.getShard(conf)
	itm := this.tryGet(sd, conf)
	if itm != nil {
		err = itm.acquire(conf)
		if err != nil {
			this.Release(itm)
			return nil, err
		}
		return itm, nil
	}

	sd.lk.Lock()
	itm = sd.lst[conf]
	if itm == nil {
		itm = &ObjectRef{owner: this, key: conf}
		sd.lst[conf] = itm
	}
	itm.incRef()
	sd.lk.Unlock()

	err = itm.acquire(conf)
	if err != nil {
		this.Release(itm)
		return nil, err
	}
	return itm, nil
}

func (this *RefObjectList) AcquireRefCount(handle any) (int, error) {
	if handle == nil {
		return 0, fmt.Errorf("handle is nil")
	}

	if ref, ok := handle.(*ObjectRef); ok {
		return int(ref.RefCount()), nil
	}

	return 0, fmt.Errorf("invalid handle")
}

func (this *RefObjectList) Release(handle any) error {
	if handle == nil {
		return fmt.Errorf("handle is nil")
	}

	itm, ok := handle.(*ObjectRef)
	if !ok {
		return fmt.Errorf("invalid handle")
	}

	n := itm.decRef()
	if n > 0 {
		return nil
	} else if n < 0 {
		return ErrorClosed
	}
	sd := this.getShard(itm.key)
	delflag := 0
	sd.lk.Lock()
	if itm.RefCount() == 0 {
		delete(sd.lst, itm.key)
		itm.owner = nil
		delflag = 1
	}
	sd.lk.Unlock()

	if delflag == 1 {
		obj := itm.inner
		itm.inner = nil
		return this.closeInner(obj)
	}
	return nil
}
